package com.fuang.multithread;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by Fuang on 2018-10-30.
 * CountDownLatch和CyclicBarrier都能够实现线程之间的等待，都适用于主线程等待所有子线程运行结束后继续往下运行的场景
 * CountDownLatch和CyclicBarrier区别：
 * 1，CountDownLatch无法重置，CyclicBarrier可以重复利用，适用于线程池
 * 2，CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后，它才执行；而CyclicBarrier一般用于一组线程互相等待至某个状态，然后这一组线程再同时执行；
 */
public class CountDownLatchAndCyclicBarrier {

    public static void main(String[] args) {
        CountDownLatchAndCyclicBarrier test = new CountDownLatchAndCyclicBarrier();
        test.countDown();

        test.cyclicBarrier();
    }

    /**
     * CountDownLatch原理 （功能类似join方法）
     * 1，通过CountDownLatch的构造函数设置计数器初始值
     * 2，主线程启动子线程后调用await方法，等待计数器归0
     * 3，每个子线程启动运行完任务后，调用countDown方法同步操作计数器-1
     * 4，当每个子线程的运行结束，计数器就归0，唤醒主线程，主线程继续执行
     */
    public void countDown() {
        CountDownLatch latch = new CountDownLatch(2);

        // 线程1
        new Thread(new Worker(latch)).start();

        // 线程2
        new Thread(new Worker(latch)).start();

        try {
            System.out.println("主线程等待子线程运行");
            latch.await(); // 等待线程1和线程2运行结束 再往后运行
            System.out.println("所有子线程运行结束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("主线程继续运行");
    }

    public class Worker implements Runnable {
        private CountDownLatch latch;

        Worker(CountDownLatch latch) {
            this.latch = latch;
        }

        @Override
        public void run() {
            System.out.println("子线程" + Thread.currentThread().getName() + " 运行 ");

            try {
                Thread.sleep(1000l); // 模拟运行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            latch.countDown();
            System.out.println("子线程" + Thread.currentThread().getName() + " 运行结束 ");
        }
    }

    /**
     * CyclicBarrier原理 （功能类似join方法）
     * 1，通过CyclicBarrier的构造函数设置线程数量
     * 2，主线程启动子线程
     * 3，每个子线程启动运行完任务后，调用barrier.await();等待其他字线程运行到屏障点
     * 4，当达到设置的线程数量的子线程运行到屏障点，子线程开始往下执行知道线程执行结束
     * 5，若CyclicBarrier实例化时有设置子线程都到达屏障点后的执行任务，则在所有到大屏障点后执行主线程对应的任务
     * PS:CyclicBarrier是可以重复使用
     */
    public void cyclicBarrier() {
        CyclicBarrier barrier = new CyclicBarrier(2, new BarrierWorker());
        ExecutorService executor = Executors.newFixedThreadPool(2);

        for (int i = 0; i < 10; i++) {
            executor.execute(new SubBarrierWorker(barrier));
        }
    }

    /**
     * 主线程执行任务
     */
    public class BarrierWorker implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "所有子线程运行结束");

            System.out.println(Thread.currentThread().getName() + "主线程继续运行");
        }
    }

    /**
     * 每个子线程执行任务
     */
    public class SubBarrierWorker implements Runnable {
        private CyclicBarrier barrier;

        SubBarrierWorker(CyclicBarrier barrier) {
            this.barrier = barrier;
        }

        @Override
        public void run() {
            System.out.println("子线程" + Thread.currentThread().getName() + " 运行 ");

            try {
                Thread.sleep(1000l); // 模拟运行
                barrier.await(); // 等待其他线程运行结束
            } catch (BrokenBarrierException | InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("子线程" + Thread.currentThread().getName() + " 运行结束 ");
        }
    }
}
